package org.koin.core;

import org.koin.core.annotation.KoinInternalApi;
import org.koin.core.component.KoinScopeComponent;
import org.koin.core.error.*;
import org.koin.core.logger.EmptyLogger;
import org.koin.core.logger.Level;
import org.koin.core.logger.Logger;
import org.koin.core.module.Module;
import org.koin.core.parameter.ParametersDefinition;
import org.koin.core.qualifier.Qualifier;
import org.koin.core.qualifier.TypeQualifier;
import org.koin.core.registry.PropertyRegistry;
import org.koin.core.registry.ScopeRegistry;
import org.koin.core.scope.Scope;
import org.koin.mp.KoinPlatformTools;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;


@KoinInternalApi
public class Koin {

    private Logger logger = new EmptyLogger();

    @KoinInternalApi
    private PropertyRegistry propertyRegistry = new PropertyRegistry(this);

    @KoinInternalApi
    private ScopeRegistry scopeRegistry = new ScopeRegistry(this);

    private HashSet<Module> modules = new HashSet<>();

    public Logger getLogger() {
        return logger;
    }

    @KoinInternalApi
    public void setupLogger(Logger logger) {
        this.logger = logger;
    }

    @KoinInternalApi
    public Scope getRootScope() {
        return scopeRegistry.getRootScope();
    }

    /**
     * TODO Lazy inject a Koin instance(java 不支持lazy)
     *
     * @return Lazy instance of type T
     */
//    public <T> Lazy<T> inject() {
//
//    }

    /**
     * TODO Lazy inject a Koin instance if available(java 不支持lazy)
     *
     * @return Lazy instance of type T or null
     */
//    public <T> Lazy<T> injectOrNull() {
//
//    }

    /**
     * Get a Koin instance
     *
     * @param clazz
     * @param qualifier
     * @param parameters
     */
    public <T> T get(
            Class<T> clazz,
            Qualifier qualifier,
            ParametersDefinition parameters
    ) throws DefinitionParameterException, NoBeanDefFoundException, ClosedScopeException {
        return scopeRegistry.getRootScope().get(clazz, qualifier, parameters);
    }

    /**
     * Get a Koin instance
     *
     * @param clazz
     * @param parameters
     */
    public <T> T get(
            Class<T> clazz,
            ParametersDefinition parameters
    ) throws DefinitionParameterException, NoBeanDefFoundException, ClosedScopeException {
        return this.get(clazz, null, parameters);
    }

    /**
     * Get a Koin instance
     *
     * @param clazz
     * @param qualifier
     */
    public <T> T get(
            Class<T> clazz,
            Qualifier qualifier
    ) throws DefinitionParameterException, NoBeanDefFoundException, ClosedScopeException {
        return this.get(clazz, qualifier, null);
    }

    /**
     * Get a Koin instance
     *
     * @param clazz
     */
    public <T> T get(
            Class<T> clazz
    ) throws DefinitionParameterException, NoBeanDefFoundException, ClosedScopeException {
        return this.get(clazz, null, null);
    }

    /**
     * Get a Koin instance if available
     *
     * @param clazz
     * @param qualifier
     * @param parameters
     * @return instance of type T or null
     */
    public <T> T getOrNull(
            Class<T> clazz,
            Qualifier qualifier,
            ParametersDefinition parameters
    ) {
        return scopeRegistry
                .getRootScope()
                .getOrNull(clazz, qualifier, parameters);
    }

    /**
     * Get a Koin instance if available
     *
     * @param clazz
     * @return instance of type T or null
     */
    public <T> T getOrNull(
            Class<T> clazz
    ) {
        return this.getOrNull(clazz, null, null);
    }

    /**
     * Declare a component definition from the given instance
     * This result of declaring a single definition of type T,
     * returning the given instance
     *
     * @param instance       The instance you're declaring.
     * @param qualifier      Qualifier for this declaration
     * @param secondaryTypes List of secondary bound types
     * @param override       Allows to override a previous
     *                       declaration of the same type (default to false).
     */
    public <T> void declare(
            Class<T> clazz,
            T instance,
            Qualifier qualifier,
            List<Class> secondaryTypes,
            boolean override
    ) throws DefinitionOverrideException {
        List<Class> firstType = new ArrayList<>();
        firstType.add(instance.getClass());
        firstType.addAll(secondaryTypes);
        scopeRegistry
                .getRootScope()
                .declare(clazz, instance, qualifier, firstType, override);
    }

    /**
     * Declare a component definition from the given instance
     * This result of declaring a single definition of type T, returning the given instance
     *
     * @param instance The instance you're declaring.
     */
    public <T> void declare(Class<T> clazz, T instance) throws DefinitionOverrideException {

        this.declare(clazz, instance, null, new ArrayList<>(), false);
    }

    /**
     * Get a all instance for given inferred class (in primary or secondary type)
     *
     * @return list of instances of type T
     */
    public <T> List<T> getAll(Class<T> clazz) {
        return scopeRegistry
                .getRootScope()
                .getAll(clazz);
    }

    /**
     * Get instance of primary type P and secondary type S
     * (not for scoped instances)
     *
     * @return instance of type S
     */
    public <S, P> S bind(
            Class<S> sClass,
            Class<P> pClass,
            ParametersDefinition parameters
    ) throws NoBeanDefFoundException {
        return scopeRegistry.getRootScope()
                .bind(sClass, pClass, parameters);
    }

    public void createEagerInstances() {
        scopeRegistry.getRootScope().createEagerInstances();
    }

    /**
     * Create a Scope instance
     *
     * @param scopeId
     * @param qualifier
     */
    public Scope createScope(
            String scopeId,
            Qualifier qualifier,
            Object source
    ) throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- create scope - id:'" + scopeId + "' q:" + qualifier);
        }
        return scopeRegistry.createScope(scopeId, qualifier, source);
    }

    /**
     * Create a Scope instance
     *
     * @param scopeId
     * @param qualifier
     */
    public Scope createScope(
            String scopeId,
            Qualifier qualifier
    ) throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        return this.createScope(scopeId, qualifier, null);
    }

    /**
     * Create a Scope instance
     *
     * @param clazz
     * @param scopeId
     */
    public <T> Scope createScope(Class<T> clazz, String scopeId, Object source)
            throws ScopeAlreadyCreatedException,
            NoScopeDefFoundException {

        TypeQualifier qualifier = new TypeQualifier(clazz);
        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- create scope - id:'" + scopeId + "' q:" + qualifier);
        }
        return scopeRegistry.createScope(scopeId, qualifier, source);
    }

    /**
     * Create a Scope instance
     *
     * @param clazz
     * @param scopeId
     */
    public <T> Scope createScope(Class<T> clazz, String scopeId)
            throws
            ScopeAlreadyCreatedException,
            NoScopeDefFoundException {
        TypeQualifier qualifier = new TypeQualifier(clazz);
        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- create scope - id:'" + scopeId + "' q:" + qualifier);
        }
        return scopeRegistry.createScope(scopeId, qualifier, null);
    }

    /**
     * Create a Scope instance
     *
     * @param clazz
     */
    public <T> Scope createScope(Class<T> clazz) throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        TypeQualifier qualifier = new TypeQualifier(clazz);
        String scopeId = KoinPlatformTools.INSTANCE().generateId();
        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- create scope - id:'" + scopeId + "' q:" + qualifier);
        }
        return scopeRegistry.createScope(scopeId, qualifier, null);
    }

    /**
     * Create a Scope instance
     *
     * @param clazz
     */
    public <T extends KoinScopeComponent> Scope createScope(Class<T> clazz, T t)
            throws ScopeAlreadyCreatedException,
            NoScopeDefFoundException {
        TypeQualifier qualifier = new TypeQualifier(clazz);
        String scopeId = KoinScopeComponent.getScopeId(t);
        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- create scope - id:'" + scopeId + "' q:" + qualifier);
        }
        return scopeRegistry.createScope(scopeId, qualifier, null);
    }

    /**
     * Get or Create a Scope instance
     *
     * @param scopeId
     * @param qualifier
     * @param source
     */
    public Scope getOrCreateScope(String scopeId, Qualifier qualifier, Object source)
            throws ScopeAlreadyCreatedException,
            NoScopeDefFoundException {

        Scope scope = scopeRegistry.getScopeOrNull(scopeId);
        if (scope == null) {
            scope = createScope(scopeId, qualifier, source);
        }
        return scope;
    }

    public Scope getOrCreateScope(String scopeId, Qualifier qualifier) throws ScopeAlreadyCreatedException, NoScopeDefFoundException {

        return getOrCreateScope(scopeId, qualifier, null);
    }

    /**
     * Get or Create a Scope instance
     *
     * @param scopeId
     */
    public <T> Scope getOrCreateScope(Class<T> tClass, String scopeId) throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        TypeQualifier qualifier = new TypeQualifier(tClass);
        Scope scope = scopeRegistry.getScopeOrNull(scopeId);
        if (scope == null) {
            scope = createScope(scopeId, qualifier, null);
        }
        return scope;
    }

    /**
     * get a scope instance
     *
     * @param scopeId
     */
    public Scope getScope(String scopeId) throws ScopeNotCreatedException {
        Scope scope = scopeRegistry.getScopeOrNull(scopeId);
        if (scope == null) {
            throw new ScopeNotCreatedException("No scope found for id '" + scopeId + "'");
        }
        return scope;
    }

    /**
     * get a scope instance
     *
     * @param scopeId
     */
    public Scope getScopeOrNull(String scopeId) {
        return scopeRegistry.getScopeOrNull(scopeId);
    }

    /**
     * Delete a scope instance
     */
    public void deleteScope(String scopeId) {
        scopeRegistry.deleteScope(scopeId);
    }

    /**
     * Retrieve a property
     *
     * @param key
     * @param defaultValue
     */
    public <T> T getProperty(String key, T defaultValue) {
        T property = propertyRegistry.getProperty(key);
        if (property != null) {
            return property;
        } else {
            return defaultValue;
        }
    }

    /**
     * Retrieve a property
     *
     * @param key
     */
    public <T> T getProperty(String key) {
        return propertyRegistry.getProperty(key);
    }

    /**
     * Save a property
     *
     * @param key
     * @param value
     */
    public void setProperty(String key, Object value) {
        propertyRegistry.saveProperty(key, value);
    }

    /**
     * Delete a property
     *
     * @param key
     */
    public void deleteProperty(String key) {
        propertyRegistry.deleteProperty(key);
    }

    /**
     * Close all resources from context
     */
    public void close() {
        modules.forEach(new Consumer<Module>() {
            @Override
            public void accept(Module module) {
                module.setLoaded(false);
            }
        });
        modules.clear();
        scopeRegistry.close();
        propertyRegistry.close();
    }

    public void loadModules(List<Module> modules,
                            boolean createEagerInstances) throws DefinitionOverrideException {
        this.modules.addAll(modules);
        scopeRegistry.loadModules(modules);
        if (createEagerInstances) {
            createEagerInstances();
        }
    }

    public void loadModules(List<Module> modules) throws DefinitionOverrideException {
        this.loadModules(modules, false);
    }

    public void unloadModules(List<Module> modules, boolean createEagerInstances) {
        scopeRegistry.unloadModules(modules);
        this.modules.removeAll(modules);
        if (createEagerInstances) {
            createEagerInstances();
        }
    }

    public void unloadModules(List<Module> modules) {
        this.unloadModules(modules, false);
    }

    public ScopeRegistry getScopeRegistry() {
        return scopeRegistry;
    }

    public PropertyRegistry getPropertyRegistry() {
        return propertyRegistry;
    }
}
